/**
 * MegaMek - Copyright (C) 2005 Ben Mazur (bmazur@sev.org)
 *
 *  This program is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the Free
 *  Software Foundation; either version 2 of the License, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 *  for more details.
 */

package megamek.common.weapons;

import java.io.Serializable;
import java.util.Enumeration;
import java.util.Vector;

import megamek.common.AmmoType;
import megamek.common.Building;
import megamek.common.Compute;
import megamek.common.Coords;
import megamek.common.Entity;
import megamek.common.HitData;
import megamek.common.IGame;
import megamek.common.Report;
import megamek.common.TagInfo;
import megamek.common.TargetRoll;
import megamek.common.Targetable;
import megamek.common.ToHitData;
import megamek.common.actions.ArtilleryAttackAction;
import megamek.common.actions.WeaponAttackAction;
import megamek.server.Server;
import megamek.server.Server.DamageType;

/**
 * The Class ArtilleryWeaponIndirectHomingHandler.
 */
public class ArtilleryWeaponIndirectHomingHandler extends
ArtilleryWeaponIndirectFireHandler implements Serializable {

	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = -7243477723032010917L;

	/**
	 * Instantiates a new artillery weapon indirect homing handler.
	 *
	 * @param toHit the to hit
	 * @param weaponAction the weapon action
	 * @param gameInterface the game interface
	 * @param server the server
	 */
	public ArtilleryWeaponIndirectHomingHandler(ToHitData toHit,
			WeaponAttackAction weaponAction, IGame gameInterface, Server server) {
		super(toHit, weaponAction, gameInterface, server);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see megamek.common.weapons.AttackHandler#handle(int, java.util.Vector)
	 */
	@Override
	public boolean handle(IGame.Phase phase, Vector<Report> vPhaseReport) {
		if (!cares(phase)) {
			return true;
		}
		ArtilleryAttackAction artilleryAction = (ArtilleryAttackAction) weaponAction;
		if (phase == IGame.Phase.PHASE_TARGETING) {
			if (!handledAmmoAndReport) {
				addHeat();
				// Report the firing itself
				weaponHandlerReport = new Report(3121);
				weaponHandlerReport.indent();
				weaponHandlerReport.newlines = 0;
				weaponHandlerReport.subject = subjectId;
				weaponHandlerReport.add(wtype.getName());
				weaponHandlerReport.add(artilleryAction.turnsTilHit);
				vPhaseReport.addElement(weaponHandlerReport);
				Report.addNewline(vPhaseReport);
				handledAmmoAndReport = true;
			}
			// if this is the last targeting phase before we hit,
			// make it so the firing entity is announced in the
			// off-board attack phase that follows.
			if (artilleryAction.turnsTilHit == 0) {
				announcedEntityFiring = false;
			}
			return true;
		}
		if (artilleryAction.turnsTilHit > 0) {
			artilleryAction.turnsTilHit--;
			return true;
		}
		Entity entityTarget;
		if (game.getPhase() == IGame.Phase.PHASE_OFFBOARD) {
			convertHomingShotToEntityTarget();
			entityTarget = (artilleryAction.getTargetType() == Targetable.TYPE_ENTITY) ? (Entity) artilleryAction
					.getTarget(game)
					: null;
		} else {
			entityTarget = (Entity) target;
		}
		final boolean targetInBuilding = Compute.isInBuilding(game,
				entityTarget);

		// Which building takes the damage?
		Building bldg = game.getBoard().getBuildingAt(target.getPosition());

		// Report weapon attack and its to-hit value.
		weaponHandlerReport = new Report(3115);
		weaponHandlerReport.indent();
		weaponHandlerReport.newlines = 0;
		weaponHandlerReport.subject = subjectId;
		weaponHandlerReport.add(wtype.getName());
		if (entityTarget != null) {
			weaponHandlerReport.addDesc(entityTarget);
		} else {
			weaponHandlerReport.messageId = 3120;
			weaponHandlerReport.add(target.getDisplayName(), true);
		}
		vPhaseReport.addElement(weaponHandlerReport);

		int hitValue = toHit.getValue();
		switch (hitValue) {
		case TargetRoll.IMPOSSIBLE:
			vPhaseReport.addElement(buildToHitDatarReport(3135, 0, subjectId, toHit));
			return false;

		case TargetRoll.AUTOMATIC_FAIL:
			vPhaseReport.addElement(buildToHitDatarReport(3140, 0, subjectId, toHit));
			break;

		case TargetRoll.AUTOMATIC_SUCCESS:
			vPhaseReport.addElement(buildToHitDatarReport(3145, 0, subjectId, toHit));
			break;

		default:
			// roll to hit
			vPhaseReport.addElement(buildToHitDatarReport(3150, 0, subjectId, toHit));
			break;
		}

		// dice have been rolled, thanks
		vPhaseReport.addElement(buildIntegerReport(3155, 0, subjectId, roll));


		// do we hit?
		bMissed = roll < toHit.getValue();

		// are we a glancing hit?
		if (game.getOptions().booleanOption("tacops_glancing_blows")) {
			if (roll == toHit.getValue()) {
				bGlancing = true;
				weaponHandlerReport = new Report(3186);
				weaponHandlerReport.subject = subjectId;
				weaponHandlerReport.newlines = 0;
				vPhaseReport.addElement(weaponHandlerReport);
			} else {
				bGlancing = false;
			}
		} else {
			bGlancing = false;
		}

		//Set Margin of Success/Failure.
		toHit.setMoS(roll-Math.max(2,toHit.getValue()));
		bDirect = game.getOptions().booleanOption("tacops_direct_blow") && ((toHit.getMoS()/3) >= 1) && (entityTarget != null);
		if (bDirect) {
			weaponHandlerReport = new Report(3189);
			weaponHandlerReport.subject = attackEntity.getId();
			weaponHandlerReport.newlines = 0;
			vPhaseReport.addElement(weaponHandlerReport);
		}

		// we may still have to use ammo, if direct fire
		if (!handledAmmoAndReport) {
			addHeat();
		}

		// Any necessary PSRs, jam checks, etc.
		// If this boolean is true, don't report
		// the miss later, as we already reported
		// it in doChecks
		boolean missReported = doChecks(vPhaseReport);
		if (missReported) {
			bMissed = true;
		}
		nDamPerHit = wtype.getRackSize();


		// copperhead gets 10 damage less than standard
		if (((AmmoType)ammo.getType()).getAmmoType() != AmmoType.T_ARROW_IV) {
			nDamPerHit -= 10;
		}

		// Do we need some sort of special resolution (minefields, artillery,
		if (specialResolution(vPhaseReport, entityTarget, bMissed)) {
			return false;
		}

		if (bMissed && !missReported) {
			reportMiss(vPhaseReport);

			// Works out fire setting, AMS shots, and whether continuation is
			// necessary.
			if (!handleSpecialMiss(entityTarget, targetInBuilding, bldg,
					vPhaseReport)) {
				return false;
			}
		}
		int hits = 1;
		int nCluster = 1;
		if (((entityTarget != null) && (entityTarget.getTaggedBy() != -1)) && artilleryAction.getCoords() != null) {
				toHit.setSideTable(entityTarget.sideTable(artilleryAction.getCoords()));
		}

		// The building shields all units from a certain amount of damage.
		// The amount is based upon the building's CF at the phase's start.
		int bldgAbsorbs = 0;
		if (targetInBuilding && (bldg != null)) {
			bldgAbsorbs = (int) Math.ceil(bldg.getPhaseCF(target.getPosition()) / 10.0);
		}
		if ((bldg != null) && (bldgAbsorbs > 0)) {
			// building absorbs some damage
			weaponHandlerReport = new Report(6010);
			Vector<Report> buildingReport = null;
			if (entityTarget != null) {
				weaponHandlerReport.subject = entityTarget.getId();
				weaponHandlerReport.add(bldgAbsorbs);
				vPhaseReport.addElement(weaponHandlerReport);
				buildingReport = server.damageBuilding(bldg,
						nDamPerHit, entityTarget.getPosition());
				for (Report report : buildingReport) {
					report.subject = entityTarget.getId();
				}
			}
			vPhaseReport.addAll(buildingReport);
		}
		nDamPerHit -= bldgAbsorbs;

		// Make sure the player knows when his attack causes no damage.
		if (nDamPerHit == 0) {
			weaponHandlerReport = new Report(3365);
			weaponHandlerReport.subject = subjectId;
			vPhaseReport.addElement(weaponHandlerReport);
			return false;
		}
		if (!bMissed && (entityTarget != null)) {
			handleEntityDamage(entityTarget, vPhaseReport, bldg, hits,
					nCluster, nDamPerHit, bldgAbsorbs);
			server.creditKill(entityTarget, attackEntity);
		}
		Coords coords = target.getPosition();
		int ratedDamage = 5; // splash damage is 5 from all launchers
		bldg = null;
		bldg = game.getBoard().getBuildingAt(coords);
		bldgAbsorbs = (bldg != null) ? bldg.getPhaseCF(coords) / 10 : 0;
		bldgAbsorbs = Math.min(bldgAbsorbs, ratedDamage);
		// assumption: homing artillery splash damage is area effect.
		// do damage to woods, 2 * normal damage (TW page 112)
		handleClearDamage(vPhaseReport, bldg, ratedDamage * 2, bSalvo);
		ratedDamage -= bldgAbsorbs;
		if (ratedDamage > 0) {
			for (Enumeration<Entity> impactHexHits = game.getEntities(coords); impactHexHits
					.hasMoreElements();) {
				Entity entity = impactHexHits.nextElement();
				if (!bMissed) {
					if (entity == entityTarget) {
						continue; // don't splash the target unless missile
						// missed
					}
				}
				toHit.setSideTable(entity.sideTable(artilleryAction.getCoords()));
				HitData hit = entity.rollHitLocation(toHit.getHitTable(), toHit
						.getSideTable(), weaponAction.getAimedLocation(), weaponAction
						.getAimingMode());
				vPhaseReport.addAll(server.damageEntity(entity, hit,
						ratedDamage, false, DamageType.NONE, false, true,
						throughFront, underWater));
				server.creditKill(entity, attackEntity);
			}
		}
		Report.addNewline(vPhaseReport);
		return false;
	}

	/**
	 * Find the tagged entity for this attack Each TAG will attract a number of
	 * shots up to its priority number (mode setting) When all the TAGs are used
	 * up, the shots fired are reset. So if you leave them all on 1-shot, then
	 * homing attacks will be evenly split, however many shots you fire.
	 * Priority setting is to allocate more homing attacks to a more important
	 * target as decided by player. TAGs fired by the enemy aren't eligible, nor
	 * are TAGs fired at a target on a different map sheet.
	 */
	protected void convertHomingShotToEntityTarget() {
		ArtilleryAttackAction artilleryction = (ArtilleryAttackAction) weaponAction;

		final Coords targetCoords = target.getPosition();
		Entity entityTarget = null;

		TagInfo info = null;
		Entity tagger = null;
		int bestDistance = Integer.MAX_VALUE;
		Vector<TagInfo> v = game.getTagInfo();

		for (int pass = 0; pass < 2; pass++) {
			int bestIndex = -1;
			for (int i = 0; i < v.size(); i++) {
				info = v.elementAt(i);
				tagger = game.getEntity(info.attackerId);
				if ((info.shots < info.priority) && !attackEntity.isEnemyOf(tagger)) {
					entityTarget = game.getEntity(info.targetId);
					if ((entityTarget != null) && entityTarget.isOnSameSheet(targetCoords)) {
						if (targetCoords.distance(entityTarget.getPosition()) < bestDistance) {
							bestIndex = i;
							bestDistance = targetCoords.distance(entityTarget
									.getPosition());
							if (!game.getOptions().booleanOption(
									"a4homing_target_area")) {
								break; // first will do if mapsheets can't
								// overlap
							}
						}
					}
				}
			}
			if (bestIndex != -1) {
				info = v.elementAt(bestIndex);
				entityTarget = game.getEntity(info.targetId);
				tagger = game.getEntity(info.attackerId);
				info.shots++;
				game.updateTagInfo(info, bestIndex);
				break; // got a target, stop searching
			}
			entityTarget = null;
			// nothing found on 1st pass, so clear shots fired to 0
			game.clearTagInfoShots(attackEntity, targetCoords);
		}

		if ((entityTarget == null) || (info == null)) {
			toHit = toHitDataObject(TargetRoll.IMPOSSIBLE, "no targets tagged on map sheet");
		} else if (info.missed) {
			target = entityTarget;
			setTargetIdAndType(artilleryction, entityTarget.getId(), Targetable.TYPE_ENTITY);
			toHit = toHitDataObject(TargetRoll.IMPOSSIBLE, "tag missed the target");
		} else {
			target = entityTarget;
			setTargetIdAndType(artilleryction, entityTarget.getId(), Targetable.TYPE_ENTITY);
		}
	}

	/**
	 * handleSpecialMiss Method.
	 *
	 * @param entityTarget the entity target
	 * @param targetInBuilding the target in building
	 * @param bldg the bldg
	 * @param vPhaseReport the v phase report
	 * @return true
	 * @see megamek.common.weapons.WeaponHandler#handleSpecialMiss(megamek.common.Entity,
	 * boolean, megamek.common.Building, java.util.Vector)
	 */
	@Override
	protected boolean handleSpecialMiss(Entity entityTarget,
			boolean targetInBuilding, Building bldg, Vector<Report> vPhaseReport) {
		return true;
	}

	/**
	 * buildReport Method returns a new server report.
	 * It holds information such as the report ID, who the report is about,
	 * who should see the report, and some formatting information.
	 * @param reportValue - int value of the report
	 * @param newlines - number of lines to add at the end of the report
	 * @param subjectId - the entity the reports concerns
	 * @param toHit - ToHitData data tags to be added to the report
	 * @return - the report that is generated
	 */
	protected Report buildToHitDatarReport(int reportValue, int newlines, int subjectId, ToHitData toHit) {
		weaponHandlerReport = new Report(reportValue);
		weaponHandlerReport.newlines = newlines;
		weaponHandlerReport.subject = subjectId;
		weaponHandlerReport.add(toHit.getDesc());
		return weaponHandlerReport;
	}

	/**
	 * buildIntegerReport Method returns a new server report.
	 * It holds information such as the report ID, who the report is about,
	 * who should see the report, and some formatting information.
	 * @param reportValue - integer value of the report
	 * @param newlines - number of lines to add at the end of the report
	 * @param subjectId - the entity the reports concerns
	 * @param roll - integer value to be added to the report
	 * @return - the report that is generated
	 */
	protected Report buildIntegerReport(int reportValue, int newlines, int subjectId, int roll) {
		weaponHandlerReport = new Report(reportValue);
		weaponHandlerReport.newlines = newlines;
		weaponHandlerReport.subject = subjectId;
		weaponHandlerReport.add(roll);
		return weaponHandlerReport;
	}

	/**
	 * ToHitData Method returns a ToHitData object with the roll and description set.
	 *
	 * @param roll - roll of the object to hit
	 * @param description - description of the object to hit
	 * @return - returns the ToHitData object with the given roll and description
	 */
	protected ToHitData toHitDataObject (int roll, String description) {
		return new ToHitData(roll, description);
	}

	/**
	 * setTargetIdAndType Method set the target id and target type of the given ArtilleryAttackAction.
	 *
	 * @param artilleryction - the given ArtilleryAttackAction to get the target id and type
	 * @param id - target id
	 * @param type - target type
	 */
	protected void setTargetIdAndType(ArtilleryAttackAction artilleryction, int id, int type) {
		artilleryction.setTargetId(id);
		artilleryction.setTargetType(type);
	}

}
